package com.imageflowpro.image_flow_pro.utils;

import io.minio.*;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.springframework.core.io.InputStreamResource;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;

@Slf4j
@Builder
public class MinioUtil {
    private MinioClient minioClient;
    public boolean bucketExists(String bucketName){
        try {
            return minioClient.bucketExists(
                    BucketExistsArgs
                            .builder()
                            .bucket(bucketName)
                            .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            return false;
        }
    }
    /*判断 bucket是否存在，不存在时创建*/
    public void bucketIsExists(String bucketName) throws Exception {
        boolean exists = minioClient.bucketExists(
                BucketExistsArgs
                        .builder()
                        .bucket(bucketName)
                        .build()
        );
        if (!exists) {
            minioClient.makeBucket(
                    MakeBucketArgs
                            .builder()
                            .bucket(bucketName)
                            .build()
            );
        }

    }

    /*获取全部bucket*/
    public List<Bucket> getAllBuckets() throws Exception {
        return minioClient.listBuckets();
    }

    //文件上传，multipartFile为上传的文件 bucketName为要上传的目标桶名 返回上传后的文件名
    // fileName如果不为空，说明要使用指定的文件名上传，相同的文件名会覆盖
    public String minioUpload(MultipartFile file, String fileName, String bucketName) {
        try {
            // fileName如果为空，说明要使用随机文件名上传
            if (fileName == null) {
                fileName = file.getOriginalFilename();
                fileName=UUID.randomUUID()+fileName.substring(fileName.lastIndexOf("."));
            }
            //获取要上传文件的输入流
            InputStream inputStream = file.getInputStream();
            //该对象包含了文件流、对象名、分片信息等重要数据
            PutObjectArgs objectArgs =
                    PutObjectArgs
                        .builder()
                            //桶名
                        .bucket(bucketName)
                            //对象名，即存入时使用的文件名
                        .object(fileName)
                            //构建InputStream，最后一个参数为分片大小，-1即使用默认值：5MB
                            // 分片大小不能小于5MB，大于5GB，分片数量不能超过10000
                            //第二个为对象大小，不能超过5TiB
                        .stream(inputStream, file.getSize(), -1)
                            //设定文件类型
                        .contentType(file.getContentType())
                        .build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
            //返回上传使用的文件名，可以用于下载或者预览图片
            return fileName;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }



    /*下载文件 originalName为要下载的文件名*/
    public void downloadFile(String bucketName, String objectName, HttpServletResponse response) throws Exception {
        // 参数校验
        if (StringUtils.isBlank(bucketName) || StringUtils.isBlank(objectName)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid parameters");
            return;
        }

        try (InputStream inputStream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build())) {

            // 1. 获取文件元数据
            StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build());

            // 2. 内容类型处理
            String contentType = stat.contentType();
            if (StringUtils.isBlank(contentType)) {
                contentType = Files.probeContentType(Paths.get(objectName));
            }
            response.setContentType(StringUtils.defaultString(contentType, "application/octet-stream"));

            // 3. 文件名编码处理
            String filename = URLEncoder.encode(stat.object(), "UTF-8")
                    .replaceAll("\\+", "%20");

            // 4. 安全下载头设置
            String contentDisposition = "attachment"; // 默认下载
            if (contentType.startsWith("image/") ||
                    contentType.startsWith("text/") ||
                    contentType.equals("application/pdf")) {
                contentDisposition = "inline"; // 直接显示
            }
            response.setHeader("Content-Disposition",
                    String.format("%s; filename=\"%s\"; filename*=UTF-8''%s",
                            contentDisposition,
                            filename,
                            filename));

            // 5. 安全头设置
            response.setHeader("X-Content-Type-Options", "nosniff");
            response.setHeader("Content-Security-Policy", "default-src 'self'");

            // 6. 流式传输
            try (OutputStream out = response.getOutputStream()) {
                byte[] buffer = new byte[8192]; // 8KB缓冲区
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
            }

        } catch (ErrorResponseException e) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found");
        } catch (MinioException e) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Storage error");
        }
    }

    // 辅助方法：获取安全文件名
    private String sanitizeFilename(String filename) {
        return filename.replaceAll("[^a-zA-Z0-9\\.\\-]", "_")
                .replaceAll("\\.\\.", "_");
    }



    /*删除桶*/
    public void deleteBucketName(String bucketName) {
        try {
            if (StringUtils.isBlank(bucketName)) {
                return;
            }

            if(bucketExists(bucketName)){
                minioClient.removeBucket(
                        RemoveBucketArgs
                                .builder()
                                .bucket(bucketName)
                                .build());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

    /*删除桶下面所有文件*/
    public void deleteBucketFile(String bucketName) {
        try {
            if (StringUtils.isBlank(bucketName)) {
                return;
            }
            if (bucketExists(bucketName)) {
                minioClient.deleteBucketEncryption(
                        DeleteBucketEncryptionArgs
                                .builder()
                                .bucket(bucketName)
                                .build()
                );
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }


    /*根据文件路径得到预览文件绝对地址，没有文件时返回null*/
    public String getPreviewFileUrl(String bucketName, String fileName) {
        try {
            return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs
                        .builder()
                        //指定返回的请求类型
                        .method(Method.GET)
                        //要查询的桶的位置
                        .bucket(bucketName)
                        //要查询的文件名称
                        .object(fileName)
                        /*
                        指定链接的过期时间，单位：秒
                        .expiry(60 * 60 * 24)
                        */
                        .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 只返回从桶名开始的路径（省略 MinIO 的地址部分）截取从桶名开始的部分和？号前的部分（省略参数）
    public String getPreviewFilePathFromBucket(String bucketName, String fileName) {
        try {
            String fullUrl = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs
                            .builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(fileName)
                            .build()
            );

            if (fullUrl == null || fullUrl.isEmpty()) {
                return null;
            }

            // 找到第一个 '/' 出现的位置，跳过协议和域名部分
            // 例如：http://minio.example.com:9000/bucket-name/file.jpg → 从 "/bucket-name/..." 开始截取
            int firstSlashIndex = fullUrl.indexOf("/", 8); // 从 "http://" 后面开始找第一个 '/'
            if (firstSlashIndex == -1) {
                return null; // 如果找不到，说明格式不对
            }

            return fullUrl.substring(firstSlashIndex).split("\\?")[0]; // 截取从桶名开始的部分和？号前的部分

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /*根据文件名删除单个文件*/
    public void deleteFile(String bucketName, String fileName) {
        try {
            if (StringUtils.isBlank(bucketName) || StringUtils.isBlank(fileName)) {
                log.error("桶名或文件名不能为空");
                return;
            }

            // 删除文件
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build()
            );

            log.info("文件删除成功: {}", fileName);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("删除文件失败: {}", e.getMessage());
        }
    }

    /*根据文件名前缀批量删除文件*/
    //前缀是区分大小写的。例如，Logs/ 和 logs/ 是不同的前缀。
    //如果不指定前缀（即 prefix("")），则会列出桶中的所有文件。
    //如果传入logs，则返回logs开头的文件，而不是logs/下的文件
    public void deleteFilesByPrefix(String bucketName, String prefix) {
        try {
            if (StringUtils.isBlank(bucketName)) {
                log.error("桶名不能为空");
                return;
            }

            // 列出桶中所有匹配前缀的对象
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix) // 文件名前缀
                            .build()
            );

            for (Result<Item> result : results) {
                Item item = result.get();
                String objectName = item.objectName(); // 获取对象名
                log.info("正在删除文件: {}", objectName);

                // 删除文件
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .build()
                );
            }
            log.info("批量删除完成，前缀为: {}", prefix);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("批量删除文件失败: {}", e.getMessage());
        }
    }


    /**
     * 下载 MinIO 中多个文件并打包为 ZIP 返回给前端
     *
     * @param bucketName 存储桶名称
     * @param fileNames 文件名列表
     * @return ResponseEntity<InputStreamResource>
     */
    public ResponseEntity<InputStreamResource> downloadFilesAsZip(String bucketName, List<String> fileNames) {
        try {
            // 1. 从 MinIO 获取 ZIP 输入流
            ByteArrayInputStream zipStream = generateZipStream(bucketName, fileNames);

            // 2. 构建响应体
            InputStreamResource resource = new InputStreamResource(zipStream);

            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=files.zip")
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(resource);

        } catch (Exception e) {
            throw new RuntimeException("压缩包生成失败", e);
        }
    }

    /**
     * 根据文件名列表生成 ZIP 流
     */
    private ByteArrayInputStream generateZipStream(String bucketName, List<String> fileNames)
            throws Exception {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(byteArrayOutputStream);

        for (String fileName : fileNames) {
            try (InputStream inputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build()
            )) {
                ZipArchiveEntry zipEntry = new ZipArchiveEntry(fileName);
                zipOutputStream.putArchiveEntry(zipEntry);

                byte[] buffer = new byte[8192];
                int len;
                while ((len = inputStream.read(buffer)) > 0) {
                    zipOutputStream.write(buffer, 0, len);
                }
                zipOutputStream.closeArchiveEntry();
            }
        }

        zipOutputStream.finish();
        zipOutputStream.close();

        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    }

}

